package com.wewins.fota.service;

import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.wewins.fota.aop.Operator;
import com.wewins.fota.bean.bo.FotaServer;
import com.wewins.fota.bean.bo.InvalidFile;
import com.wewins.fota.bean.bo.filter.FilterCond;
import com.wewins.fota.bean.dto.PageReq;
import com.wewins.fota.bean.dto.PageRst;
import com.wewins.fota.bean.po.PkgVersion;
import com.wewins.fota.common.constant.FotaCode;
import com.wewins.fota.common.constant.RedisConst;
import com.wewins.fota.common.util.CommonUtil;
import com.wewins.fota.mapper.PkgVersionMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.io.File;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Slf4j
@Service
public class PkgVersionService implements BasicService<PkgVersion, PkgVersion> {
    private final PkgVersionMapper mapper;

    private final RedisTemplate<String, Object> redisTemplate;

    private final RedisCacheService redisCacheService;

    private final FotaServerService fotaServerService;

    @Autowired
    public PkgVersionService(
            PkgVersionMapper mapper,
            RedisTemplate<String, Object> redisTemplate,
            RedisCacheService redisCacheService,
            FotaServerService fotaServerService) {
        this.mapper = mapper;
        this.redisTemplate = redisTemplate;
        this.redisCacheService = redisCacheService;
        this.fotaServerService = fotaServerService;
    }

    @Override
    public <K extends FilterCond> PageRst<PkgVersion> queryAll(PageReq<K> pageReq) {
        PageHelper.startPage(pageReq);
        return PageRst.<PkgVersion>builder().build().fillBy(PageInfo.of(mapper.queryAll(pageReq.getFilterBy())));
    }

    @Override
    public PkgVersion findById(long id) {
        return mapper.findById((int) id);
    }

    @Override
    @Operator(FotaCode.CMD_ADD_PKG)
    public PkgVersion insert(String username, PkgVersion data) {
        data.setCreateTime(System.currentTimeMillis());
        data.refreshUser(username);
        mapper.insert(data);
        redisCacheService.cacheOnePkg(data);
        redisTemplate.opsForHash().delete(RedisConst.REDIS_KEY_INVALID_FILE, data.constructFileCacheKey());
        return data;
    }

    public List<String> findAllProducts() {
        return mapper.findAllProducts();
    }

    public List<String> findVersionsByProduct(String product) {
        return mapper.findVersionsByProduct(product);
    }

    @Override
    @Operator(FotaCode.CMD_UPDATE_PKG)
    public Integer update(String username, PkgVersion data) {
        data.setUpdateTime(System.currentTimeMillis());
        data.refreshUser(username);
        int count = mapper.update(data);
        redisCacheService.removeOldCache4Pkg(data);
        redisCacheService.cacheOnePkg(data);
        return count;
    }

    @Operator(FotaCode.CMD_UPDATE_PKG)
    public int update(String username, List<PkgVersion> pkgVersionList) {
        for (PkgVersion pkgVersion : pkgVersionList) {
            pkgVersion.setUpdateTime(System.currentTimeMillis());
            pkgVersion.refreshUser(username);
        }
        int count = mapper.updateByBatch(pkgVersionList);
        pkgVersionList.forEach(item -> {
            redisCacheService.removeOldCache4Pkg(item);
            redisCacheService.cacheOnePkg(item);
        });
        return count;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Operator(FotaCode.CMD_DELETE_PKG)
    public Integer deleteById(String username, long id) {
        PkgVersion version = mapper.findById((int) id);
        if (version == null) {
            return 0;
        }
        log.info("current plan is:" + version);
        redisCacheService.removeOldCache4Pkg(version);
        this.deleteSourceFile(version);
        return mapper.delete(version);
    }

    private void deleteSourceFile(PkgVersion version) {
        String domain = "";
        String relativePath = "";
        Matcher matcher = Pattern.compile("https?://([^/]*)/.*/([^/]*/[^/]*)").matcher(version.getUri());
        if (matcher.find()) {
            domain = matcher.group(1);
            relativePath = matcher.group(2);
        }
        if (StringUtils.isEmpty(domain) || StringUtils.isEmpty(relativePath)) {
            return;
        }
        FotaServer server = fotaServerService.queryByDomain(domain);
        InvalidFile invalidFile = InvalidFile.builder()
                .serverKey(server.getKey())
                .path(server.getSourcePath() + File.separator + relativePath)
                .uploadTime(System.currentTimeMillis()).build();
        redisTemplate.opsForHash().put(
                RedisConst.REDIS_KEY_INVALID_FILE,
                relativePath.replace("/", ":"),
                JSONObject.toJSONString(invalidFile)
        );
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Integer delete(String username, PkgVersion data) {
        return this.deleteById(username, data.getId());
    }

    public List<String> queryProductsByCache(String subName) {
        return this.queryByCache("product", subName, "*");
    }

    public List<String> queryVersionsByCache(String product, String subVersionStr) {
        return this.queryByCache("version", product, subVersionStr);
    }

    public List<String> queryByCache(String field, String subName, String subVersion) {
        subName = "product".equals(field) ? "*" + subName + "*" : ("ALL".equals(subName) ? "*" : subName);
        subVersion = "product".equals(field) ? "*" : "*" + subVersion + "*";
        Set<String> keys = redisTemplate.keys(String.format(RedisConst.REDIS_KEY_PKG_CACHE, subName, subVersion));
        if (keys == null) {
            return new ArrayList<>();
        }
        return keys.stream().map(
                item -> {
                    Long size = redisTemplate.opsForSet().size(item);
                    if (size != null && size > 0) {
                        String dbKey = (String) redisTemplate.opsForSet().pop(item);
                        if (!StringUtils.isEmpty(dbKey)) {
                            redisTemplate.opsForSet().add(item, dbKey);
                            return (String) redisTemplate.opsForHash().get(dbKey, field);
                        }
                    }
                    return null;
                }
        ).distinct().filter(Objects::nonNull).collect(Collectors.toList());
    }

    public int existPkg(PkgVersion data, boolean forUpdate) {
        Set<Object> members = this.redisTemplate.opsForSet().members(String.format(RedisConst.REDIS_KEY_PKG_CACHE, data.getProduct(), data.getVersion()));
        if (members == null || members.size() == 0) {
            return -1;
        }
        String newFlag = data.getFlag();
        Map<String, String> newFlagMap = newFlag != null ? JSONObject.parseObject(newFlag, Map.class) : null;
        for (Object member : members) {
            String flag = (String) this.redisTemplate.opsForHash().get((String) member, "flag");
            int id = (int) this.redisTemplate.opsForHash().get((String) member, "id");
            if (forUpdate && data.getId() == id) { // exclude self when update pkg
                continue;
            }
            if (newFlag == null && flag == null) {
                return id;
            }
            if (newFlag != null && flag != null) {
                Map<String, String> flagMap = JSONObject.parseObject(flag, Map.class);
                if (CommonUtil.equalMap(newFlagMap, flagMap)) {
                    return id;
                }
            }

        }
        return -1;
    }
}
